/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2020. All rights reserved.
 */

package com.huawei.codecheck.uccp.plugin;

import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
import com.huawei.codecheck.uccp.api.GenReportFileToolExtensionPoint;
import com.huawei.codecheck.uccp.api.base.PluginExtensionPoint;
import com.huawei.codecheck.uccp.api.bean.cmd.CommandInfo;
import com.huawei.codecheck.uccp.api.bean.cmd.CommandResult;
import com.huawei.codecheck.uccp.api.bean.defect.DefectInfo;
import com.huawei.codecheck.uccp.api.bean.defect.DefectResult;
import com.huawei.codecheck.uccp.api.bean.param.ModelInfo;
import com.huawei.codecheck.uccp.api.bean.param.ToolParameterInfo;
import com.huawei.codecheck.uccp.api.bean.param.ToolRuleSetInfo;
import com.huawei.codecheck.uccp.api.bean.result.CmdReportFileResult;
import com.huawei.codecheck.uccp.api.bean.result.ReportFileResult;
import com.huawei.codecheck.uccp.api.bean.defect.DefectInfo;
import com.huawei.codecheck.uccp.common.util.FileUtil;
import com.huawei.codecheck.uccp.common.util.GsonUtil;
import com.huawei.codecheck.uccp.common.util.ReportUtil;
import com.huawei.codecheck.uccp.common.util.StringUtil;
import com.huawei.codecheck.uccp.common.util.PluginConfigUtil;
import com.huawei.codecheck.uccp.common.util.DefectReportWriter;
import com.huawei.codecheck.uccp.common.util.PathUtil;
import com.huawei.codecheck.uccp.common.util.PluginConfigUtil;
import com.huawei.codecheck.uccp.plugin.bean.ToolConfig;


import com.huawei.codecheck.uccp.plugin.cangjie.CangjieInfo;
//import com.huawei.codecheck.uccp.plugin.cangjie.Cjlint;
import com.huawei.codecheck.uccp.plugin.cangjie.ExecUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.file.PathUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pf4j.Extension;
import org.pf4j.Plugin;
import org.pf4j.PluginWrapper;
import org.pf4j.RuntimeMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.exec.CommandLine;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * demo插件
 * FIXME 开发完成后注意修改类名
 *
 * @author sunzequan
 * @since 2019-09-23
 */
//@Slf4j
public class CjLintToolsPlugin extends Plugin {
    /**
     * 插件配置, 默认继承BaseAnalysisToolConfig
     */
    private static volatile ToolConfig toolConfig;


    public CjLintToolsPlugin(PluginWrapper wrapper) {
        super(wrapper);
    }

    /**
     * 加载jar时, 执行初始化
     */
    @Override
    public void start() {
        // 加载插件配置,  默认读取插件resources/config.json文件
        ToolConfig toolConfig = PluginConfigUtil.loadPluginConfig(this.wrapper, ToolConfig.class);
        this.toolConfig = toolConfig;
        log.info("{} plugin start", toolConfig.getToolName());

        // 当运行状态为发布时, 打印插件配置
        if (RuntimeMode.DEVELOPMENT.equals(wrapper.getRuntimeMode())) {
            log.info("plugin config: {}", GsonUtil.getInstance().toJson(toolConfig));
        }
    }

    /**
     * 停止时, 可以打印日志, 释放资源等
     */
    @Override
    public void stop() {
//        LOG.info("{} plugin stop", toolConfig.getToolName());
    }

    /**
     * 实现插件接口
     * FIXME 开发完成后注意修改类名
     */
    @Extension
    public static class CjLintToolsAnalyze implements GenReportFileToolExtensionPoint {
        protected static final Logger LOG = LoggerFactory.getLogger(CjLintToolsAnalyze.class);

        static String tempDir = "";

        public CjLintToolsAnalyze() {
        }

        public String getToolName() {
            return CjLintToolsPlugin.toolConfig.getToolName();
        }

        public List<String> getFileSuffixes() {
            return CjLintToolsPlugin.toolConfig.getFileSuffixes();
        }

        public boolean isAutoFillMainBuggyCode() {
            return true;
        }

        public DefectResult parseDefect(CmdReportFileResult cmdReportFileResult) throws Exception {

            List<DefectInfo> defectInfos = new ArrayList();
            File reportFile = new File(cmdReportFileResult.getReportFilePath());
            if (reportFile.exists()) {
                parse(reportFile, CjLintToolsPlugin.toolConfig, defectInfos);
            }

            return new DefectResult(defectInfos, new ArrayList<String>(0));
        }

        public static void modifyField(String filePath,
                                       String targetField,
                                       String newValue) throws IOException {
            List<String> lines = Files.readAllLines(Paths.get(filePath));
            List<String> modified = new ArrayList<>();

            for (String line : lines) {
                modified.add(line.replace(targetField, newValue));
            }

            Files.write(Paths.get(filePath),
                    modified,
                    StandardOpenOption.TRUNCATE_EXISTING);
        }

        private static void parse(File reportFile, ToolConfig toolConfig, List<DefectInfo> defectInfos) throws Exception {
            try {
                LOG.info("旧ID={}, 新ID={}", tempDir, "code");
                modifyField(reportFile.getPath(), tempDir, "code");
                Gson gson = new Gson();
                try (
                    FileReader fileReader = new FileReader(reportFile);
                    JsonReader reader = new JsonReader(fileReader)
                ) {
                    CangjieInfo[] arr = gson.fromJson(reader, CangjieInfo[].class);
                    for (int i = 0; i < arr.length; i++) {
                        DefectInfo defectInfo = new DefectInfo(toolConfig.getToolName());
                        CangjieInfo info = arr[i];
                        defectInfo.setBuggyFilePath(info.getFile());
                        defectInfo.setMainBuggyLine(info.getLine());
                        defectInfo.setColumn(info.getColumn());
                        defectInfo.setDescription(info.getDescription());
                        defectInfo.setDefectType(info.getDescription().split(":")[0]);
                        defectInfo.setExtraMsg("");
                        defectInfos.add(defectInfo);
                    }
                }
            } catch (IOException e) {
                LOG.error("parse file failed", e);
            }
        }


        public CommandResult generateCommand(ToolParameterInfo toolParameterInfo) throws Exception {
            List<CommandInfo> commandInfos = new ArrayList();
            Set<String> fileSet = new HashSet<>(); // 源码文件集合
            String projectRoot = toolParameterInfo.getProjectRoot();
            for (String f : toolParameterInfo.getSourceFiles()) {
                fileSet.add(f);
            }

            HashSet<String> hashSet = new HashSet<>();  // 排除文件集合
            if (toolParameterInfo.getExclusions() != null) {
                for (String f : toolParameterInfo.getExclusions().split(",")) {
                    hashSet.add(projectRoot + f);
                }
            } else {
                LOG.warn("排除集合为空");
            }

            File projectRootFile = new File(projectRoot);

            Path source = Paths.get(projectRoot);

            tempDir = UUID.randomUUID().toString();

            Path tempCopyFile = Paths.get(projectRootFile.getParent() + File.separator + tempDir);

            if (!Files.exists(tempCopyFile)) {
                LOG.info("创建临时文件夹 {}", tempCopyFile);
                Files.createDirectories(tempCopyFile);
            }

            if (copyWithStructure(source, tempCopyFile, fileSet, hashSet)) {
                LOG.error("仓颉文件已全部屏蔽");
                throw new IllegalStateException("仓颉文件已全部屏蔽");
            }

            String shellPath = toolConfig.getToolPath().toLowerCase() + File.separator + (SystemUtils.IS_OS_LINUX ? "start.sh" : "start.bat");

            toolParameterInfo.getIncludePaths();
            int stepNo = 1;
            String reportFile = ReportUtil.genReportFile(toolParameterInfo.getReportDirPath(), "cjlint_", stepNo, ".json");
            FileUtil.delFile(new File(reportFile));

            String cmd = String.format(Locale.ROOT, "%s %s %s", shellPath, StringUtil.addQuotation(reportFile), tempCopyFile.toString());
            CommandLine cmdLine = CommandLine.parse(cmd);
            LOG.info("cmd line: {}", cmdLine.toString().replace("/", "//"));
            commandInfos.add(new CommandInfo(cmdLine, reportFile));
            return new CommandResult(commandInfos, new ArrayList<String>());
        }

        static boolean copyWithStructure(Path source, Path target, Set<String> set1, Set<String> set2) throws IOException {
            AtomicBoolean aBoolean = new AtomicBoolean(true);
            Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                        throws IOException {
                    Path relativePath = source.relativize(dir);
                    Path destination = target.resolve(relativePath);
                    LOG.info("创建文件夹 {}", destination);
                    Files.createDirectories(destination);
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                        throws IOException {
                    if (set1.contains(file.toString()) && file.toString().endsWith(".cj")) {
                        if (!set2.contains(file.toString())) {
                            aBoolean.set(false);
                            Path relativePath = source.relativize(file);
                            Path destination = target.resolve(relativePath);
                            LOG.info("复制文件 {}", destination.toString().replace("/", "\\"));
                            Files.copy(file, destination, StandardCopyOption.COPY_ATTRIBUTES);
                        } else {
                            LOG.info("屏蔽文件 {}", file.toString());
                        }
                    } else{
                        LOG.warn("非仓颉文件: {}", file);
                    }
                    return FileVisitResult.CONTINUE;
                }
            });
            return aBoolean.get();
        }

        public boolean isValidateExpectCode() {
            return true;
        }
    }

}
